﻿using Sustainalytics.Alerts.AlertsEngine.Model;
using Sustainalytics.ClientDW.Entities.Universes;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.ClientDW.DataAccessLayer;
using Sustainalytics.Claims;
using System.Security.Claims;
using System.Linq.Expressions;
using Sustainalytics.Portfolios.BL;
using Sustainalytics.Utils;

namespace Sustainalytics.Alerts.AlertsEngine
{
    public class AlertsSubscription : IAlertsSubscription
    {
        private const string _allowAll = "allow.all";
        private const string _allowRead = "allow.read";
        private const string _controversiesClaim = "company/controversies";
        private const string _alertClaim = "controversyalert";
        private const string _alertSemiMonthlyClaim = "controversyalert/semi-monthly";
        private readonly IOperationClaimsRetriever _claimsRetriever;
        private readonly IRepositoryCollection<User> _userRepCollection;
        private readonly IRepositoryCollection<Account> _accountRepCollection;
        private readonly IRepositoryCollection<UniverseCompany> _universeRepositoryCollection;
        private readonly IUniversesRepository _featureUnivRepo;
        private readonly IPortfolioReadHandler _portfolioManager;


        public AlertsSubscription(IOperationClaimsRetriever claimsRetriever, IRepositoryCollection<User>  userRepCollection,
            IRepositoryCollection<Account>  accountRepCollection, IRepositoryCollection<UniverseCompany> universeRepositoryCollection,
            IUniversesRepository featureUnivRepo, IPortfolioReadHandler portfolioManager)
        {
            _claimsRetriever = claimsRetriever;
            _userRepCollection = userRepCollection;
            _accountRepCollection= accountRepCollection;
            _universeRepositoryCollection = universeRepositoryCollection;
            _featureUnivRepo = featureUnivRepo;
            _portfolioManager = portfolioManager;
        }

        public IList<SubscribeToPortfolio> GetSubscriptions(int frequency, IList<Guid> unservedClients, string connectionStringClientPlatform)
        {
            Expression<Func<SubscribeToPortfolio, bool>> wherePredicate = (x => x.Frequency == (int)frequency && x.Enabled);
            if (unservedClients != null && unservedClients.Count > 0)
                wherePredicate = (x => x.Frequency == (int)frequency && x.Enabled && unservedClients.Contains(x.UserId));

            return new RepositoryCollection<SubscribeToPortfolio>(connectionStringClientPlatform).ReadAllWhere(wherePredicate);
        }

        public SubscriptionDto GetSubscriptionDetails(SubscribeToPortfolio subscriber, int frequency, Guid sessionId,
            string connectionStringClientUniverse, string connectionStringClientPlatform, string clientDWConnectionString, List<Guid> companiesWithAlerts,
            IPorfolioNamesCache porfolioNamesCache, IPortfolioCompanyCache portfolioCompanyCache, IFeatureUniversesCache featureUniversesCache)
        {
           
            var specificAlertClaim = _alertSemiMonthlyClaim;

            User user;
            if (!HasUserAccess(subscriber.UserId, _userRepCollection, out user))
            {
                AlertsEngineSerilog.Log.SubscriptionRejectedBecauseUserAccess(sessionId, subscriber.Id.ToString());
                return new SubscriptionDto()
                {
                    UserId = subscriber.UserId,
                    Enabled = false
                };
            }

            if (user.AccountId != subscriber.AccountId)//artefact
            {
                AlertsEngineSerilog.Log.SubscriptionRejectedBecauseUserHasInvalidAccount(sessionId, subscriber.Id.ToString());
                return new SubscriptionDto()
                {
                    UserId = subscriber.UserId,
                    Enabled = false
                };
            }

            bool disabled = false;
            //check claims:
            Account account;
            IEnumerable<Claim> claims;
            if (!HasClaims(subscriber, _accountRepCollection, specificAlertClaim, out claims, out account))
            {
                AlertsEngineSerilog.Log.SubscriptionWithoutClaims(sessionId, subscriber.Id.ToString());
                return new SubscriptionDto()
                {
                    UserId = subscriber.UserId,
                    Enabled = false
                };
            }

            //check universes feature:
            var companiesEnabledForFeature = GetCompaniesEnabledForFeature(subscriber, _universeRepositoryCollection, portfolioCompanyCache, featureUniversesCache,
                companiesWithAlerts, _featureUnivRepo, _portfolioManager);
            if (!companiesEnabledForFeature.Any())
            {
                AlertsEngineSerilog.Log.SubscriptionWithoutFeatureUniverses(sessionId, subscriber.Id.ToString());
                disabled = true;
            }

            var portNames = subscriber.PortfolioIds.Select(portId => porfolioNamesCache.GetPortfolioName(portId, _portfolioManager));
            //fill in data:
            AlertsEngineSerilog.Log.SubscriptionAccepted(sessionId, subscriber.Id.ToString());

            return new SubscriptionDto()
            {
                AccountName = account.Name,
                HasAccestToCompanyControversies = claims.Any(c => c.Type.ToLowerInvariant() == _controversiesClaim && c.Value.ToLowerInvariant() == _allowRead),
                CompaniesWithPortfolios = disabled ? new Dictionary<Guid, string>() : portfolioCompanyCache.GetPortfolioListForCompanies(subscriber, companiesEnabledForFeature, _portfolioManager),
                UserEmail = user.Email,
                UserFullName = user.FullName,
                UserId = user.Id,
                PortfolioNames = portNames.Any() ? portNames.Aggregate((i, j) => i + ", " + j) : string.Empty,
                Enabled = true
            };
        }

        private bool HasUserAccess(Guid userId, IRepositoryCollection<User> _userRepCollection, out User userInfo)
        {
            userInfo = _userRepCollection.ReadAllWhere(u => u.Id == userId).FirstOrDefault();
            if (userInfo == null)
                return false;

            return userInfo.Status == Status.Active;
        }

        private bool HasClaims(SubscribeToPortfolio subscriber, IRepositoryCollection<Account> accountRepo, string specificAlertClaim,
            out IEnumerable<Claim> claims, out Account account)
        {
            account = accountRepo.Read(subscriber.AccountId);

            claims = _claimsRetriever.GetUserClaims(account);

            return (claims.Where(c =>
                    (c.Type.ToLowerInvariant() == _alertClaim && c.Value.ToLowerInvariant() == _allowAll)
                    || c.Type.ToLowerInvariant() == specificAlertClaim && c.Value.ToLowerInvariant() == _allowRead).Count() == 2);
        }

        private static List<Guid> GetCompaniesEnabledForFeature(SubscribeToPortfolio subscriber,
            IRepositoryCollection<UniverseCompany> universeCompanyRepo, IPortfolioCompanyCache portfolioCompanyCache, IFeatureUniversesCache featureUniversesCache,
            List<Guid> companiesWithAlerts, IUniversesRepository featureUnivRepo, IPortfolioReadHandler portfolioManager)
        {
            var featureUniverses = featureUniversesCache.GetFeatureUniverses(subscriber.AccountId, featureUnivRepo);
            if (!featureUniverses.Any())//no feature universes for this account
            {
                return new List<Guid>();
            }

            IEnumerable<Guid> companies;
            if (subscriber.PortfolioIds.Contains(Guid.Empty))
            {
                // a subscription for "All companies"
                // we could consider all companies with alerts and next intersect them with feature universe companies for this account
                companies = companiesWithAlerts;
                //fill in the portfolioCompanyCache because we will need it
                subscriber.PortfolioIds.ForEach(portId => portfolioCompanyCache.GetCompaniesForPortfolio(portId, companiesWithAlerts, portfolioManager));
            }
            else
            {
                companies = subscriber.PortfolioIds.SelectMany(portId => portfolioCompanyCache.GetCompaniesForPortfolio(portId, companiesWithAlerts, portfolioManager)).Distinct();
            }

            if (!companies.Any())
            {
                return new List<Guid>();
            }

            var companiesEnabledForFeature = universeCompanyRepo.ReadAllWhere(
                uc => companies.Contains(uc.CompanyId) && uc.AccountId == subscriber.AccountId,
                includeFields: new string[] { "CompanyId", "UniverseId" })
                    .Where(uc => featureUniverses.Contains(uc.UniverseId)).Select(uc => uc.CompanyId).Distinct().ToList();

            return companiesEnabledForFeature;
        }
    }
}
